package run;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import run.core.NIL;

/**
 * Abstract class which represents the generalized Cell. All other functional cells must
 * extend this class. The Cell is responsible for managing bindings, facets, and
 * intracellular transportation of data subcells.
 * 
 * User-defined cells "speciate" from this stem cell by providing their own
 * Structural Definition (a blueprint) that is realized into an actual Lattice of
 * subcells that govern the cell's behavior.
 * 
 * All Cells perform a computation when stimulated on their (satisfied) facets.
 * 
 * Cells manage the set of facets that they own and manage the flow of computation
 * through their internal subcell structure -- the lattice.
 * 
 * 
 * @author Thomas Chau 
 *
 */
public class Cell  {

	protected boolean INPUT_FACET = true;
	protected boolean OUTPUT_FACET = false;
	
	
	protected String cellTypeName;
	protected String labelName;
	private Lattice lattice;

	/* Facets ready to export */
	private Queue<Facet> outReadyFacets = new LinkedList<Facet>();
	
	//Hashmap of all this cell's facets from name -> facet
	protected HashMap<String, Facet> facets = new HashMap<String, Facet>();
	
	//ArrayList of all this cell's output facets
	private ArrayList<Facet> outFacets = new ArrayList<Facet>();
	//ArrayList of all this cell's input facets
	private ArrayList<Facet> inFacets = new ArrayList<Facet>();
	
	// debug flag
	private boolean reporting = false;
	
	
	public Cell(String typeName, String labelName) {
		this.cellTypeName = typeName;
		this.labelName = labelName;
	}
	
	public Cell(String name, Lattice lattice) {
		super();
		this.cellTypeName = name;
		this.lattice = lattice;
	}
	
	public void realize() {
		
	}
	

	public void exec() {
		report("\n ************************ \n Writing DOT " + cellTypeName);
		DotConverter dot = new DotConverter("swarm.graph");
		dot.startDotFile();
		dot.convert(this);
		dot.endDotFile();
		
		report("\n ************************ \n Executing " + cellTypeName);
		stimulate(null);
	}

	/**
	 * Special Case: for MAIN, stimmer will be NULL
	 * STIMMER IS A -BORDER- facet of THIS cell.
	 * @returns the final output facets that this stimulation deposited onto, 
	 * awaiting export to neighbors 
	 */ 
	public List<Facet> stimulate(Facet stimmer)
	{
		Set<Facet> ourOutFacetsRdy = new HashSet<Facet>();
		
		// MAKE ALL OTHER INFACETS OF THE CELL GIVE NIL TO THEIR BRANCHES
		for (Facet altInFacet : inFacets) {
			if (altInFacet != stimmer) {
				altInFacet.nillify();
				outReadyFacets.add(altInFacet);
			}
		}
		
		// Intrinsic Cells need to push their OutFacets to the OUTREADY queue -- 
		// e.g. dangling ints, initializers, etc.
		Set<Cell> subcells = lattice.getSubcells();
		for (Cell intrinsic : subcells) {
			if (intrinsic.isLiteral())
				outReadyFacets.addAll(intrinsic.getOutFacets());
		}
		
		outReadyFacets.add(stimmer);
		
		// While OUTREADY facet queue (facets with outbound ready data) isn't empty,
		while (outReadyFacets.size() > 0) {
			// pop a facet off the OUTREADY
			Facet nextRdy = outReadyFacets.remove();
			while (nextRdy == null && !outReadyFacets.isEmpty()) 
				nextRdy = outReadyFacets.remove();
			if (outReadyFacets.isEmpty() && nextRdy == null)
				break;
			
				// 	propagate values to where they belong (according to Lattice def)
				// 		(stimulations will occur between here and 
				//   	facets will add themselves to end of a 'ready' list in the parent cell)
				// - done output facets of a subcell that just finished (add to outReadyFacets)
				// - distinguish these from border facets of THIS going out.
			
			// port the values from the stimulating facet
			List<Object> outboundValues = nextRdy.dispatchOutbound();
			
			// bring them to the destination facets, replicating the data for each
			List<Facet> destinations = lattice.getDestinations(nextRdy);
			
			// if any of the destinations belong to us, DONE -- it's one of the facets
			// we should give to enclosing cell as outReadyFacet. 
			for (Facet dest : destinations) {
				boolean facetReady = dest.receive(nextRdy, outboundValues);

				// if it's one of our exiting border facets
				if (dest.getOwner() == this) {
					ourOutFacetsRdy.add(dest);
				}
				else if (facetReady) {
					// stimulate, add subcell's ready output facets to be propagated next.
					List<Facet> subcell_doneFacets = dest.getOwner().stimulate(dest);
					for (Facet f : subcell_doneFacets) {
						outReadyFacets.add(f);
					}
				}
			}
		}

		List<Facet> outs = getOutFacets();
		for (Facet f : outs) {
			if (f.isReady()) {
				ourOutFacetsRdy.add(f);
			}
		}
		
		ourOutFacetsRdy = stripNils(ourOutFacetsRdy);
		return new LinkedList<Facet>(ourOutFacetsRdy);
	}
	
	private Set<Facet> stripNils(Set<Facet> ourOutFacetsRdy) {
		for (Facet f : ourOutFacetsRdy) {
			f.clearNils();
		}
		return ourOutFacetsRdy;
	}
	
	public List<Facet> getFacets() {
		return new ArrayList<Facet>(facets.values());
	}
	
	/**
	 * List of input facets of this cell. 
	 * @return
	 */
	public List<Facet> getInFacets() {
		return inFacets;
	}
	
	/**
	 * List of output facets of this cell.
	 * @return
	 */
	public List<Facet> getOutFacets() {
		return outFacets;
	}
	
	public void addFacet(Facet f, boolean isInputFacet)
	{
		report("   Adding facet " + f + " to " + toString());
		facets.put(f.getName(), f);
		if (isInputFacet)
			inFacets.add(f);
		else
			outFacets.add(f);
	}
	
	
	//Prints out the dot syntax for this cell
	public String toDot(String prefix)
	{
		String retString = "";
		/*retString += "subgraph cluster_" +  prefix + cellTypeName + hashCode() + " {\n";
		
		retString += "label = \"" + cellTypeName + "\";\n";
		HashSet<Cell> subcells = new HashSet<Cell>();
		
		for(Facet f : dependencies.keySet())
		{
			subcells.add(f.getOwner());
		}
		
		subcells.remove(this);
		
		//Make all the subgraphs
		prefix += cellTypeName + hashCode();
		for(Cell c : subcells)
		{
//			retString += c.toDot(prefix);
		}		
		
		//Print my facets with proper, simple labels
		for(Facet f : facets.values())
		{
			retString += prefix + f.getName().substring(1) + " [label = \"" + f.getName() + "\"]\n;";
		}
		
		//Put in all the bindings of this cell
		if (lattice != null) {
			for(Bind b : lattice.getBindings())
			{
				Facet left = b.getLeft();
				Facet right = b.getRight();
	
				boolean toggleLeft = false;
				boolean toggleRight = false;
				if(left.getOwner() == this)
					toggleLeft = true;
				if(right.getOwner() == this)
					toggleRight = true;
					
				retString += prefix;
				if(!toggleLeft)
					retString += left.getOwner().cellTypeName + left.getOwner().hashCode();
				retString += left.getName().substring(1);	
				//retString += " [label = \"" + left.getName().substring(1) + "\"] ";
				retString += " -> ";
				retString += prefix;
				if(!toggleRight)
					retString += right.getOwner().cellTypeName + right.getOwner().hashCode();
				retString += right.getName().substring(1);
				//retString += " [label = \"" + right.getName().substring(1) + "\"] ";				
				retString += ";\n";
			}
		}
		retString += "}\n";*/
		return retString;
	}
	
	public void report(String s) {
		if (reporting)
			System.out.println(s);
	}
	
	public boolean isLiteral()
	{
		return false;
	}
	
	public String toString() {
		return cellTypeName;
	}

	public void setLabelName(String labelName) {
		this.labelName = labelName;
	}
	
}

